package com.curvebreakers.wheresmystuff.model;

import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import android.os.Handler;
import android.os.Looper;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class DatabaseModel {
	public static final String DATABASE_URL = "http://immense-bayou-7996.herokuapp.com";
	public final static int SUCCESS = 1, FAILURE = 0;
	
	/**
	 * Authenticates the specified email and password with the database. If the info is valid,
	 * it will notify the listener with an onResultReceived message (User obj). Otherwise,
	 * it will send the listener an onRequestFailure message.
	 * 
	 * obj - the logged in User
	 * 
	 * @param email 
	 * @param password
	 * @param listener The listener to be called when finished
	 */
	public static void authenticate(final String email, final String password, final DBResultListener listener) {
		new Thread(new Runnable() {
		    public void run() {	
		    	Object param;
		    	boolean success;
				try {
					HttpPost post = new HttpPost(DATABASE_URL+"/user/authenticate.php");
					
					ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
			        nameValuePairs.add(new BasicNameValuePair("email", email));
			        nameValuePairs.add(new BasicNameValuePair("password", password));
		        	
			        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			        
			        JSONObject root = requestToJSONObject(post);
			        
			        if (root.get("status").equals("success")) {
			        	success = true;
			        	param = User.createFromJSON(root.getJSONObject("user"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
		        } catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Error with internet connection";
		        }
				notifyListener(success, param, listener);
		    }
		}).start();
 	}	
	
	/**
	 * Creates or updates the user in the database. If user is successfully saved, it will 
	 * notify the listener with an onResultReceived message, with the updated <code>User</code> object. 
	 * Otherwise, it will send the listener an onRequestFailure message.
	 * 
	 * obj - updated User
	 * 
	 * @param user 
	 * @param listener The listener to be called when finished
	 */
	public static void saveUser(final User user, final DBResultListener listener) {
		new Thread(new Runnable() {
		    public void run() {	
		    	Object param;
		    	boolean success;
				try {
					HttpPost post = new HttpPost(DATABASE_URL+"/user/save.php");
					
					ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(7);
					nameValuePairs.add(new BasicNameValuePair("id", String.valueOf(user.getId())));
			        nameValuePairs.add(new BasicNameValuePair("email", user.getEmail()));
			        if (user.getPassword() != null)
			        	nameValuePairs.add(new BasicNameValuePair("password", user.getPassword()));
			        nameValuePairs.add(new BasicNameValuePair("admin", String.valueOf(user.isAdmin())));
			        nameValuePairs.add(new BasicNameValuePair("locked", String.valueOf(user.isLocked())));
			        nameValuePairs.add(new BasicNameValuePair("phone_number", user.getPhoneNum()));
		        	
			        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			      
			        JSONObject root = requestToJSONObject(post);

			        if (root.get("status").equals("success")) {
			        	success = true;
			        	param = User.createFromJSON(root.getJSONObject("user"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
			        
		        } catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Error with internet connection";		        	
		        }
				notifyListener(success, param, listener);
		    }
		}).start();
	}
	
	/**
	 * Deletes the user from the database.
	 * 
	 * obj - null
	 * 
	 * @param user The User to be deleted
	 * @param listener The listener to be called when finished
	 */
	public static void deleteUser(final User user, final DBResultListener listener) {
		new Thread(new Runnable() {
		    public void run() {	
		    	Object param;
		    	boolean success;
				try {
					HttpPost post = new HttpPost(DATABASE_URL+"/user/delete.php");
					
					ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(7);
					nameValuePairs.add(new BasicNameValuePair("id", String.valueOf(user.getId())));
		        	
			        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			        
			        JSONObject root = requestToJSONObject(post);

			        if (root.get("status").equals("success")) {
			        	success = true;
			        	param = null;
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
			        
		        } catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Error with internet connection";		        	
		        }
				notifyListener(success, param, listener);
		    }
		}).start();
	}

	/**
	 * Creates or updates an form entry in the database. If the entry is successfully saved, it will 
	 * notify the listener with an onResultReceived message, with the updated <code>Entry</code> object. 
	 * Otherwise, it will send the listener an onRequestFailure message.
	 *
	 * @param entry The <code>Entry</code> to save or update in the database. 
	 * @param listener The listener to be called when finished.
	 */
	public static void saveEntry(final Entry entry, final DBResultListener listener) {
		new Thread(new Runnable() {
		    public void run() {	
		    	Object param;
		    	boolean success;
				try {
					HttpPost post = new HttpPost(DATABASE_URL+"/entry/save.php");
					
					ArrayList<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(8);
					//Add the serializable elements of the entry
					nameValuePairs.add(new BasicNameValuePair("id", String.valueOf(entry.getId())));
					nameValuePairs.add(new BasicNameValuePair("user_id", String.valueOf(entry.getUserId())));
					nameValuePairs.add(new BasicNameValuePair("title", String.valueOf(entry.getTitle())));
					nameValuePairs.add(new BasicNameValuePair("description", String.valueOf(entry.getDescription())));
					nameValuePairs.add(new BasicNameValuePair("reward", String.valueOf(entry.getReward())));
					nameValuePairs.add(new BasicNameValuePair("type", String.valueOf(entry.getEntryType())));
					nameValuePairs.add(new BasicNameValuePair("category", String.valueOf(entry.getCategory())));
					nameValuePairs.add(new BasicNameValuePair("resolved", String.valueOf(entry.isResolved())));
					nameValuePairs.add(new BasicNameValuePair("zip_code", String.valueOf(entry.getZipCode())));
					//nameValuePairs.add(new BasicNameValuePair("location", String.valueOf(entry.getLocation())));
		        	
			        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			        				    
			        JSONObject root = requestToJSONObject(post);
			        
			        if (root.get("status").equals("success")) {
			        	success = true;
			        	param = entry;
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
		        } catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Was not able to update post!\nPlease try again";		        	
		        }
				notifyListener(success, param, listener);
		    }
		}).start();
	}
	
	/**
	 * If successful, this method calls notifyListener passing a JSON Array of entries that
	 * match the request specifications.
	 * 
	 * @param query the search specifications appended onto the DATABASE_URL
	 * @param listener The listener to be called when finished.
	 */
	public static void getEntries(final String query, final DBResultListener listener) {
		new Thread(new Runnable() {
			public void run() {
				Object param = null;
				boolean success;
				try {
					String finalQuery = query.replace(" ", "%20");
					HttpGet get = new HttpGet(DATABASE_URL+"/entry/search.php?"+finalQuery);
					JSONObject root = requestToJSONObject(get);
					
					 if (root.get("status").equals("success")) {
			        	success = true;
			        	param = Entry.createListFromJSON(root.getJSONArray("entries"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
				  
				} catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Was not able to retrieve list.\nPlease wait a moment and try again";
				}
				notifyListener(success, param, listener);
			}
		}).start();
	}
	
	/**
	 * Identical to getEntries except that it returns only items created or found by the
	 * User whose ID is given as the parameter.
	 * 
	 * @param user_id The user's id
	 * @param listener The listener to be called when finished.
	 */
	public static void getUserEntries(final int user_id, final DBResultListener listener) {
		new Thread(new Runnable() {
			public void run() {
				Object param = null;
				boolean success;
				try {
					HttpGet get = new HttpGet(DATABASE_URL+"/entry/getUserEntries.php?user_id="+user_id);
					JSONObject root = requestToJSONObject(get);
					
					 if (root.get("status").equals("success")) {
			        	success = true;
			        	param = Entry.createListFromJSON(root.getJSONArray("entries"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
				  
				} catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Was not able to retrieve list.\nPlease wait a moment and try again";
				}
				notifyListener(success, param, listener);
			}
		}).start();
	}
	
	/**
	 * Returns a List of entries that are possible matches for the given user's lost
	 * and found entries.
	 * 
	 * @param user_id The user's id
	 * @param listener The listener to be called when finished.
	 */
	public static void getUserMatches(final int user_id, final DBResultListener listener) {
		new Thread(new Runnable() {
			public void run() {
				Object param = null;
				boolean success;
				try {
					HttpGet get = new HttpGet(DATABASE_URL+"/entry/match.php?user_id="+user_id);
					JSONObject root = requestToJSONObject(get);
					
					 if (root.get("status").equals("success")) {
			        	success = true;
			        	param = Entry.createListFromJSON(root.getJSONArray("entries"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
				  
				} catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Was not able to retrieve list.\nPlease wait a moment and try again";
				}
				notifyListener(success, param, listener);
			}
		}).start();
	}
	
	/**
	 * Returns a List of all Users in the Database.
	 * 
	 * @param listener The listener to be called when finished.
	 */
	public static void getAllUsers(final DBResultListener listener) {
		new Thread(new Runnable() {
			public void run() {
				Object param = null;
				boolean success;
				try {
					HttpGet get = new HttpGet(DATABASE_URL+"/user/getAll.php");
					JSONObject root = requestToJSONObject(get);
					 if (root.get("status").equals("success")) {
			        	success = true;
			        	param = User.createListFromJSON(root.getJSONArray("users"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }				  
				} catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Was not able to retrieve list.\nPlease wait a moment and try again";
				}
				notifyListener(success, param, listener);
			}
		}).start();
	}
	
	/**
	 * Gets the User with the specified user ID
	 * 
	 * @param userID The user ID to get
	 * @param listener
	 */
	public static void getUser(final int userID, final DBResultListener listener) {
		new Thread(new Runnable() {
			public void run() {
				Object param = null;
				boolean success;
				try {
					HttpGet get = new HttpGet(DATABASE_URL+"/user/get.php?id="+userID);
					JSONObject root = requestToJSONObject(get);
					
					 if (root.get("status").equals("success")) {
			        	success = true;
			        	param = User.createFromJSON(root.getJSONObject("user"));
			        } else {
			        	success = false;
			        	param = root.get("message").toString();
			        }
			        if (root.has("error")) {
			        	System.out.println(root.get("error"));
			        }
				  
				} catch (Exception e) {
		        	System.out.println(e.toString());
		        	success = false;
		        	param = "Couldn't retrieve User";
				}
				notifyListener(success, param, listener);
			}
		}).start();
	}
	
	/**
	 * Called from other DatabaseModel methods with the listener as a parameter.
	 * Passes into the listener the object returned from the database if the previous
	 * method was successful.
	 * 
	 * @param success
	 * @param param
	 * @param listener
	 */
	private static void notifyListener(final boolean success, final Object param, final DBResultListener listener) {
		new Handler(Looper.getMainLooper()).post(new Runnable() {
			@Override
			public void run() {
				if (success) {
					listener.onDBResultReceived(param);
				} else {
					listener.onDBRequestFailure((String)param);
				}
			}
		});
	}
	
	/**
	 * Executes a HTTP request and returns a JSON object from its response
	 * 
	 * @param req HTTP request
	 * @return JSON object from request
	 * @throws ClientProtocolException
	 * @throws IOException
	 * @throws JSONException
	 */
	private static JSONObject requestToJSONObject(HttpRequestBase req) throws ClientProtocolException, IOException, JSONException {
		HttpClient httpclient = new DefaultHttpClient();
		HttpResponse response = httpclient.execute(req);
	    StatusLine statusLine = response.getStatusLine();
	    if(statusLine.getStatusCode() == HttpStatus.SC_OK){
	        ByteArrayOutputStream out = new ByteArrayOutputStream();
	        response.getEntity().writeTo(out);
	        out.close();
	        String responseString = out.toString();
	        return new JSONObject(responseString);
	    } else {
	        response.getEntity().getContent().close();
	        throw new IOException(statusLine.getReasonPhrase());
	    }
	}
	
}